#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <netinet/tcp.h>
#include <string.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include "sbalance.h"
#include "sbconstant.h"
#include "sbutil.h"
#include "sbmain.h"

char **sb_environ;
char **sb_argv;
int sb_argc;
char *sb_last_env;
char **sb_os_argv;
int sb_argv_max_size;   /* contain the last \0 */
extern char **environ;

struct timeval  cache_time;
char cache_str_time[sizeof("2014-12-12 12:12:12")];
const int cache_str_time_len = sizeof("2014-12-12 12:12:12");

#define LOG_DEFAULT_PATHFILENAME	"./sb.log"
#define CONF_DEFAULT_PATHFILENAME	"./sb.conf"


int sb_ncpu;
int sb_reload_seq_flag = 0;
int msgtype_start, msgtype_num = 0;

static struct sb_env sysenv;
static struct sb_cycle_env workerenv;

static struct sb_env new_sysenv;
static struct sb_cycle_env new_workerenv;

struct sb_channel *g_channel;
struct sb_env *g_sysenv;
struct sb_cycle_env *g_workerenv;

/* use for reload configure file */
struct sb_channel *g_new_channel;	
struct sb_cycle_env *g_new_workerenv;	
struct sb_env *g_new_sysenv;

#define SB_STATUS_RELOADING	1
static int sigcmd;
static int status;

void sb_destory_accept_lock(struct sb_env *sysenv, struct sb_cycle_env *workerenv);
void sb_system_close_listening(struct sb_cycle_env *env);
int sb_init_conf(struct sb_env *sysenv, struct sb_cycle_env *workerenv);

static void usage()
{
	printf( "USAGE : sbalance [-f configfilename] [-d]\n" );
	printf( "the default config is sb.conf or add '-f configfilename' argv\n" );
	printf( "all valid argv follow:\n" );
	printf( "     -f: config path/filename\n" );
	printf( "     -d: set output level debug\n" );
	printf( "     -i: set output level info\n" );
	printf( "     -e: set output level error\n" );
	printf( "     -v: show usage\n" );
}

static void sb_set_io_nobuffer(FILE *file)
{
	setvbuf(file, (char *)NULL, _IONBF, 0);
}

static int sb_get_workerno(int i)
{
	return sb_reload_seq_flag * SB_MAX_WORKERS_NUM + i; 
}

static void sb_change_log_file(FILE *file)
{
	FILE *old_file = NULL;

	old_file = SetLogFile(file);
	if (old_file != NULL) {
		fclose(old_file);
	}
}

void sb_parse_arg(struct sb_env *env, int argc, char **argv)
{
	int i;

	if (argc > 1) {
		for (i = 1; i < argc; i++) {
			if (strcmp(argv[i], "-v") == 0) {
				usage();
				exit(1);
			}
			else if (strcmp(argv[i], "-conf") == 0) {
				if (i + 1 < argc) {
					strcpy(env->config_pathfilename, argv[i+1]);
					continue;
				}
				usage();
				exit(1);
			}
			else if (strcmp(argv[i], "-t") == 0) {
				struct sb_cycle_env new_workerenv;
				memset(&new_workerenv, 0x00, sizeof(new_workerenv));
				sb_change_log_file(stderr);

				if (argc == 2) {
					strcpy(env->config_pathfilename, "./sb.conf");
				} 	
				else if (argc == 3) {
					strcpy(env->config_pathfilename, argv[i+1]); 
				}
				else {
					fprintf(stderr, "check configure file:arguments not correct\n");
					usage();
					exit(1);
				}

				(void)sb_conf_check_valid(&new_workerenv, env->config_pathfilename);

				exit(1);
			}
			else if (strcmp(argv[i], "-w") == 0) {
				if (i + 1 < argc) {
					env->workers_num = atoi(argv[i+1]);
					continue;
				}
				usage();
				exit(1);
			}
			else if (strcmp(argv[i], "-proxy") == 0) {
				env->http_proxy = 1;
			}
			else if (strcmp(argv[i], "-log") == 0) {
				if (i + 1 < argc) {
					strcpy(env->log_pathfilename, argv[i+1]);
					continue;
				}
				usage();
				exit(1);	
			}
			else if(strcmp(argv[i], "-d") == 0) {
				env->log_level = DEBUG;
				continue;
			}
			else if(strcmp(argv[i], "-i") == 0) {
				env->log_level = DEBUG;
				continue;
			}
			else if(strcmp(argv[i], "-e") == 0) {
				env->log_level = INFO;
				continue;
			}
			else {
				usage();
				exit(1);
			}
		}
	}

	if ((env->log_file = fopen(env->log_pathfilename, "a+")) == NULL) {
		fprintf(stderr, "open systen log file failed\n");
		exit(1);
	}
	sb_set_io_nobuffer(env->log_file);

	SetLogFile(env->log_file);
	SetLogLevel(env->log_level);
}

int sb_parse_conf(struct sb_cycle_env *worker_env, char *pathfilename)
{
	if (sb_parse_main_conf(worker_env, pathfilename)) {
		ErrorOutput("parse config file [%s] error", pathfilename);	
		return SB_ERROR;
	}

	if (worker_env->http_proxy) {
		if (worker_env->rule_num > 1) {
			ErrorOutput("using http proxy mode, only one rule can be set");	
			return SB_ERROR;
		}
	}

	return SB_OK;
}

int sb_spawn_dns_process(struct sb_env *env, struct sb_channel *p_channel)
{
	int rc = 0;
	pid_t pid;

	rc = socketpair(AF_UNIX, SOCK_STREAM, 0, p_channel[0].channel);
	if (rc) {
		ErrorOutput("socketpair	failed, errno[%d]-error[%s]", errno, strerror(errno));

		return SB_ERROR;
	}

	SystemInfo("start init dns process...");
	if ((pid = fork()) == -1) {
		ErrorOutput("init dns process failed!");
		return SB_ERROR;
	} else if (pid == 0) {
		/* in child */
		FILE *dns_file = NULL;

		//while(1) {	sleep(1);	}
		sb_set_process_title("sbdns");

		memset(&p_channel[0], 0x00, sizeof(struct sb_channel));
		strcpy(p_channel[0].log_pathfilename, "dnsworker.log");

		dns_file = fopen(p_channel[0].log_pathfilename, "a+");
		sb_set_io_nobuffer(dns_file);

		if (dns_file == NULL) {
			ErrorOutput("dns process open log file failed");
			exit(1);
		}
		sb_change_log_file(dns_file);	

		p_channel[0].pid = getpid();

		if (sb_dns_init(env->msgqueueid)) {
			ErrorOutput("dns server init failed");
			exit(1);
		}

		SystemInfo("dns server start, pid[%d]",  p_channel[0].pid);
		rc = sb_dns_main_loop(env->msgqueueid);
		SystemInfo("pid[%d] dns server exit, rc=%d", p_channel[0].pid, rc);
		exit(1);
	} else {
		/* in parent */
		p_channel[0].pid = pid;
	}

	return SB_OK;	
}

/* @RETURN 1. SB_OK	2. the failed worker no */
int sb_spawn_workers_process(struct sb_cycle_env *worker_env, struct sb_channel *p_channel)
{
	int rc = 0;
	pid_t pid;
	int i = 0;

	ErrorOutput("num[%d]", worker_env->workers_num);
	for (i = 1; i <= worker_env->workers_num; i++) {
		rc = socketpair(AF_UNIX, SOCK_STREAM, 0, p_channel[i].channel);
		if (rc) {
			ErrorOutput("socketpair	failed, errno[%d]-error[%s]", errno, strerror(errno));

			return i;
		}

		SystemInfo("start init no[%d] worker process...", i);
		if ((pid  = fork()) == -1) {
			ErrorOutput("init no[%d] worker process failed", i);

			return i;
		} 
		else if (pid == 0) {
			/* in child */	
			FILE *worker_file = NULL;

			//while(1) {	sleep(1);	}
			sb_set_process_title("sbworker/%d", i);
			sb_setaffinity(i);

			memset(&p_channel[i], 0x00, sizeof(struct sb_channel));
			sprintf(p_channel[i].log_pathfilename, "worker_%d.log", i);
			worker_file = fopen(p_channel[i].log_pathfilename, "a+");
			if (worker_file == NULL) {
				ErrorOutput("worker[%d] process open log file failed", i);
				exit(1);
			}
			sb_change_log_file(worker_file);	
			sb_set_io_nobuffer(worker_file);

			p_channel[i].pid = getpid();
			worker_env->workerno = sb_get_workerno(i);
			SystemInfo("worker process pid[%d]-no[%d] start", p_channel[i].pid, i); 
			rc = sb_worker_main(worker_env);
			SystemInfo("pid[%d]-no[%d] worker process exit, rc=%d", p_channel[i].pid, i,  rc);

			exit(1);
		}	
		else {
			/* in parent */
			p_channel[i].pid = pid;
			SystemInfo("start init no[%d] pid[%d] worker process", i, pid);
		}
	}

	return SB_OK;
}

/* @RETURN: SB_OK	SB_ERROR */
int sb_respawn_child_process(struct sb_cycle_env *worker_env, struct sb_channel *p_channel, int index)
{
	int rc = 0;
	pid_t pid;

	rc = socketpair(AF_UNIX, SOCK_STREAM, 0, p_channel[index].channel);
	if (rc) {
		ErrorOutput("socketpair	failed, errno[%d]-error[%s]", errno, strerror(errno));

		return SB_ERROR;
	}


	SystemInfo("respawn child process,index[%d]", index);
	if (index == 0) {
		SystemInfo("restart dns process...");
		if ((pid = fork()) == -1) {
			ErrorOutput("reinit dns process failed!");
			return SB_ERROR;

		} else if (pid == 0) {
			/* in child */
			FILE *dns_file = NULL;
			SystemInfo("respawn dns child process pid[%d]", getpid());
			sb_set_process_title("sbdns");

			//while(1) {	sleep(1);	}
			memset(&p_channel[0], 0x00, sizeof(struct sb_channel));
			strcpy(p_channel[0].log_pathfilename, "dnsworker.log");
			dns_file = fopen(p_channel[0].log_pathfilename, "a+");

			sb_change_log_file(dns_file);
			sb_set_io_nobuffer(dns_file);

			p_channel[0].pid = getpid();

			if (sb_dns_init(g_sysenv->msgqueueid)) {
				ErrorOutput("dns server init failed");
				exit(1);
			}

			SystemInfo("dns server restart, pid[%d]", p_channel[0].pid);
			rc = sb_dns_main_loop(g_sysenv->msgqueueid);
			SystemInfo("pid[%d] dns server exit, rc=%d", p_channel[0].pid, rc);

			exit(1);
		} else {
			/* in parent */
			p_channel[0].pid = pid;
		}
		ErrorOutput("reinit dns process success, index[%d]-pid[%d]", index, pid);

	} else {
		SystemInfo("restart worker process...");
		if ((pid = fork()) == -1) {
			ErrorOutput("reinit worker process failed!");
			return SB_ERROR;

		} else if (pid == 0) {
			/* in child */
			FILE *worker_file = NULL;
			SystemInfo("respawn worker child process pid[%d]", getpid());

			sb_set_process_title("sbworker/%d", index);
			sb_setaffinity(index);

			//while(1) {	sleep(1);	}
			memset(&p_channel[index], 0x00, sizeof(struct sb_channel));
			sprintf(p_channel[index].log_pathfilename, "worker_%d.log", index);
			worker_file = fopen(p_channel[index].log_pathfilename, "a+");
			if (worker_file) {
				ErrorOutput("worker[%d] process open log file failed", index);
				exit(1);
			}

			sb_change_log_file(worker_file);
			sb_set_io_nobuffer(worker_file);

			p_channel[index].pid = getpid();
			worker_env->workerno = sb_get_workerno(index);

			SystemInfo("worker process restart, pid[%d]-no[%d]", p_channel[index].pid, index);
			rc = sb_worker_main(worker_env);
			SystemInfo("pid[%d]-no[%d] worker process exit, rc=%d", p_channel[index].pid, index,  rc);

			exit(1);
		} else {
			/* in parent */
			p_channel[index].pid = pid;
			ErrorOutput("reinit worker process success, index[%d]-pid[%d]", index, pid);
		}
	}

	return SB_OK;
}

int sb_kill_dns_server(struct sb_env *env, struct sb_channel *p_channel, int force)
{
	if (env->http_proxy == 0)
		return SB_OK;

	SystemInfo("start kill dns[%d],force[%d]...", p_channel[0].pid, force);
	if (force) {
		kill(p_channel[0].pid, SIGKILL);

	} else {
		kill(p_channel[0].pid, SIGUSR2);
	}

	return SB_OK;
}

int sb_kill_workers(struct sb_env *env, struct sb_channel *p_channel, int force)
{
	int i;

	SystemInfo("start kill worker process, force[%d]...", force);
	for (i = 1; i <= env->workers_num; i++) {
		if (p_channel[i].pid > 0) {
			SystemInfo("start kill no[%d] worker process[%d],force[%d]...", i, p_channel[i].pid, force);
			if (force) {
				kill(p_channel[i].pid, SIGKILL);

			} else {
				kill(p_channel[i].pid, SIGUSR2);
			} 
		} 
	}

	return SB_OK;
}

void sb_system_show_process(struct sb_env *g_sysenv, struct sb_channel *p_channel)
{
	int i;

        SystemInfo("++++process list+++");
        SystemInfo("master process[%d]", getpid());
        if (g_sysenv->http_proxy) {
                SystemInfo("dns process[%d]", p_channel[0].pid);
        }
        for (i = 1; i <= g_sysenv->workers_num; i++) {
                SystemInfo("worker process[%d]-[%d]", i, p_channel[i].pid);
        }
        SystemInfo("----process list----");
}

void sb_system_signal_handler(int signo)
{	
	if (signo == SIGCHLD)  {
		int status, i;
		pid_t pid;

		while (1) {
			pid = waitpid(-1, &status, WNOHANG);

			if (pid == 0 || (pid == -1 && errno == ECHILD)) {
				break;
			}
			for (i = 0; i <= g_sysenv->workers_num; i++) {
				if (g_channel[i].pid == pid) {
					SystemInfo("index[%d] - pid[%d] had exited", i, pid);
					g_channel[i].pid = -pid;
					g_channel[i].channel[0] = -1;
					g_channel[i].channel[1] = -1;
					break;
				}
			}
		}
	}
	else {
		if (sigcmd == 0) {
			if (signo == SIGUSR1)
				sigcmd = SB_SYS_CMD_QUICKEXIT;
			else if (signo == SIGUSR2)
				sigcmd = SB_SYS_CMD_SLOWEXIT;
			else if (signo == SIGHUP)  {
				sigcmd = SB_SYS_CMD_RELOADCONF; 
printf("okk\n");
			}
		}
	}
}

int sb_init_system_signal()
{
	int i;
	struct sigaction sa;

	/*	TODO: by hotmocha
		for (i = 1; i <= 64; i++) {
		signal(i, SIG_IGN);
		}
	 */
	/*
	   signal(SIGCHLD, sb_system_signal_handler); 
	   signal(SIGUSR1, sb_system_signal_handler); 
	   signal(SIGUSR2, sb_system_signal_handler); 
	   signal(SIGHUP, sb_system_signal_handler); 
	 */

	sa.sa_handler = sb_system_signal_handler;
	sigemptyset(&sa.sa_mask);
	sigaction(SIGCHLD, &sa, NULL);
	sigaction(SIGUSR1, &sa, NULL);
	sigaction(SIGUSR2, &sa, NULL);
	sigaction(SIGALRM, &sa, NULL);
	sigaction(SIGHUP, &sa, NULL);

	return SB_OK;
}

int sb_system_process_signal(struct sb_env *env, struct sb_channel *p_channel)
{
	int i;
	int rc;

	if (sigcmd == 0) {
		return SB_OK;
	}
	if (sigcmd == SB_SYS_CMD_QUICKEXIT) {
		SystemInfo("catch quick exit signal");
		if (env->http_proxy) {
			kill(p_channel[0].pid, SIGUSR1);
		}
		for (i = 1; i <= env->workers_num; i++) {
			kill(p_channel[i].pid, SIGUSR1);
		}

	} else if (sigcmd == SB_SYS_CMD_SLOWEXIT) {
		SystemInfo("catch slow exit signal");
		if (env->http_proxy) {
			kill(p_channel[0].pid, SIGUSR2);
		}
		for (i = 1; i <= env->workers_num; i++) {
			kill(p_channel[i].pid, SIGUSR2);
		}

	} else if (sigcmd == SB_SYS_CMD_RELOADCONF) {
		if (status == 0) {
			msgtype_num = 0;
			SystemInfo("catch reload configure file signal");

			/* 0x00. check & reload new configure file */
			g_new_workerenv = &new_workerenv;
			g_new_sysenv = &new_sysenv;
			
			memset(g_new_workerenv, 0x00, sizeof(struct sb_cycle_env));
			memset(g_new_sysenv, 0x00, sizeof(struct sb_env));

			memcpy(g_new_sysenv, env, sizeof(struct sb_env));

			if (sb_conf_check_valid(g_new_workerenv, env->config_pathfilename) == SB_OK) {
				memset(g_new_workerenv, 0x00, sizeof(struct sb_cycle_env));
				memset(g_new_sysenv, 0x00, sizeof(struct sb_env));
				memcpy(g_new_sysenv, env, sizeof(struct sb_env));

			#if 0
				if (sb_parse_main_conf(&new_workerenv, env->config_pathfilename) != SB_OK) {
					ErrorOutput("reload new configure file [%s] not correct, reload failed", env->config_pathfilename);
					return SB_ERROR;
				}
			#endif

				if (sb_init_conf(g_new_sysenv, g_new_workerenv) != SB_OK) {
					ErrorOutput("reload new configure file [%s] not correct, reload failed", g_new_sysenv->config_pathfilename);
					return SB_ERROR;
				}

				if (g_new_workerenv->http_proxy != env->http_proxy) {
					ErrorOutput("reload configure file [%s] not correct, can't change mode", env->config_pathfilename);
					return SB_ERROR;
				}
				
			} else {
				ErrorOutput("check new configure file [%s] not correct, reload failed", env->config_pathfilename);
				return SB_ERROR;
			}

			sb_conf_show(g_new_workerenv);

			g_new_sysenv->msgqueueid = g_sysenv->msgqueueid;
			g_new_workerenv->msgqueueid = g_sysenv->msgqueueid;

			/* 0x01. fork new worker process */
			if (sb_alloc_channel(&g_new_channel, g_new_sysenv)) {
				/* free rule memory */
				sb_free_conf(g_new_workerenv);
				ErrorOutput("alloc memory for new channel failed");
				return SB_ERROR;
			}
			SystemInfo("reload:alloc channel success!");
			if (g_new_workerenv->http_proxy) {
				memcpy(&g_new_channel[0], &g_channel[0], sizeof(struct sb_channel));
			}

			sb_reload_seq_flag = (sb_reload_seq_flag == 1) ? 0 : 1;

			/* init listening */
			rc = sb_system_init_listening(g_new_workerenv);
			if (rc) {
				sb_free_conf(g_new_workerenv);
				free(g_new_channel);

				ErrorOutput("reload:init listenging failed");

				return SB_ERROR;
			}
			SystemInfo("reload:init listening success!");

			if (sb_init_accept_lock(g_new_sysenv, g_new_workerenv) != SB_OK) {
				sb_free_conf(g_new_workerenv);
				free(g_new_channel);

				ErrorOutput("reload:create shm failed, errno[%d]-error[%s]", errno, strerror(errno));

				return SB_ERROR;
			}
			SystemInfo("reload:create(accept lock) shm shmid [%d]!", g_new_sysenv->shmid);

			/* lock accept, the worker process can't enter until unlock by master process*/
			sb_spinlock(g_new_sysenv->accept_lock);

			/* close old listening */
			sb_system_close_listening(g_workerenv);

			if (sb_spawn_workers_process(g_new_workerenv, g_new_channel)) {
				SystemInfo("reload:sb_spawn_workers_process failed");

				/* kill workers by force */
				(void)sb_kill_workers(g_new_sysenv, g_new_channel, 1);

				sb_free_conf(g_new_workerenv);
				free(g_new_channel);

				sb_destory_accept_lock(g_new_sysenv, g_new_workerenv);
				SystemInfo("remove shmid [%d]", g_new_sysenv->shmid);

				return SB_ERROR;
			}

			/* unlock new accept */
			sb_spinunlock(g_new_sysenv->accept_lock);

			msgtype_num = env->workers_num;

			/* 0x02. signal slow quit cmd to old worker process */
			sb_kill_workers(env, g_channel, 0);
			status = SB_STATUS_RELOADING; 

			SystemInfo("reload:success, waiting old worker die!");

			sb_system_show_process(g_new_sysenv, g_new_channel);
		}

	} else {
		SystemInfo("unknow sigcmd");
		sigcmd = 0;	
	}

	return SB_OK;
}

void sb_system_lock_accept(struct sb_env *env)
{
	sb_spinlock(env->accept_lock);
}

void sb_system_unlock_accept(struct sb_env *env)
{
	sb_spinunlock(env->accept_lock);
}


void sb_system_close_listening(struct sb_cycle_env *env)
{
	int i;

	for (i = 0; i < env->total_listenfd_num; i++) {
		if (env->listen[i].remain)
			continue;
		close(env->listen[i].listen_fd);
	}
}

int sb_system_init_listening(struct sb_cycle_env* env)
{
	int err = 0;
	struct sockaddr_in addr;
	struct sb_listen *li;
	struct sb_forward_rule *rule;
	struct sb_conf_simple_address *simple_addr = NULL;
	int http_proxy = 0;
	struct sb_event *e;
	int is_find;
	int i, j, k;

	for (i = 0; i < env->rule_num; i++) {
		rule = env->rules[i];

		SystemInfo("rule name [%s]  - init listening", rule->rule_name );
		for (j = 0; j < rule->listen_num; j++) {
			is_find = 0;

			simple_addr = &rule->listen_addr[j];
			li = &env->listen[env->total_listenfd_num];
			li->forward_rule = rule; 
			e = &li->listen_event;
			e->data = li;

			li->listen_address.port = simple_addr->port;
			if (simple_addr->ip1 == CONF_INT_ANY) {
				strcpy(li->listen_address.ip, "ANY");
			}
			else {
				strcpy(li->listen_address.ip, simple_addr->simple_ip);
			}

			memset(&addr, 0x00, sizeof(struct sockaddr_in));
			addr.sin_family = AF_INET;
			addr.sin_port = htons(li->listen_address.port);
			if (strcmp(li->listen_address.ip, "") == 0 || strcmp(li->listen_address.ip, "ANY") == 0) {
				addr.sin_addr.s_addr = htonl(INADDR_ANY);
			}
			else {
				if (inet_pton(addr.sin_family, li->listen_address.ip, &addr.sin_addr.s_addr) != 1) {
					ErrorOutput("inet_pton ip[%s] failed-error[%d]", li->listen_address.ip, errno);
					return SB_ERROR;	
				}
			} 
			memcpy(&li->listen_address.sockaddr, &addr, sizeof(struct sockaddr_in));

			/* reload configure file, find the old env whether had the fd */
			if (env != g_workerenv) {
				for (k = 0; k < g_workerenv->total_listenfd_num; k++) {
					if (sb_cmp_sockaddr((struct sockaddr*)&addr, (struct sockaddr*)&g_workerenv->listen[k].listen_address.sockaddr) == 0) {
						li->listen_fd = g_workerenv->listen[k].listen_fd;
						li->listen_address.port = g_workerenv->listen[k].listen_address.port;
						strcpy(li->listen_address.ip, g_workerenv->listen[k].listen_address.ip);
						memcpy(&li->listen_address.sockaddr, &addr, sizeof(struct sockaddr_in));

						g_workerenv->listen[k].remain = 1;
						is_find = 1;
						SystemInfo("rule name [%s]-ip[%s]:port[%d] reuse", rule->rule_name, li->listen_address.ip, li->listen_address.port );
						break;
					}
				}

				if (is_find) {
					env->total_listenfd_num++;
					continue;
				}
			} 

			if ((li->listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
				ErrorOutput("socket failed-error[%d]", errno);
				return SB_ERROR;
			}

			if (sb_set_nonblocking(li->listen_fd) != SB_OK) {
				ErrorOutput("set nonblocking failed-error[%d]", errno);
				return SB_ERROR;
			}

			if (sb_set_reuseaddr(li->listen_fd, 1) != SB_OK) {
				ErrorOutput("set reuseaddr failed-error[%d]", errno);
				return SB_ERROR;
			}

			if (bind(li->listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
			{
				ErrorOutput("bind ip[%s]:port[%d] failed, error[%d]", li->listen_address.ip, li->listen_address.port, errno);
				close(li->listen_fd);
				return SB_ERROR;	
			}	
			if (listen(li->listen_fd, 1024) == -1) {
				ErrorOutput("listen ip[%s]:port[%d] failed, error[%d]", li->listen_address.ip, li->listen_address.port, errno);
				close(li->listen_fd);
				return SB_ERROR;	
			}

			SystemInfo("init listen ip[%s]:port[%d], listenfd[%d] success", li->listen_address.ip, li->listen_address.port, li->listen_fd);
			env->total_listenfd_num++;
		}
	}

	if (env->http_proxy) {
		SystemInfo("******start with http proxy mode*****\n");
	}
	else {
		SystemInfo("******start with balance mode*****\n");
	}

	return SB_OK;
}

int sb_system_process_dns_read(int fd, struct sb_channel *channel)
{
	return SB_OK;
}

int sb_system_process_worker_read(int fd, struct sb_channel *channel)
{
	return SB_OK;
}

/* @ FAILED: exit(1); 
 * if arg set both in argument and configure file, the priority is configure file > argument 
 */
int sb_init_conf(struct sb_env *sysenv, struct sb_cycle_env *workerenv)
{
	int rc;
	int i;

#if 0
	sysenv->workers_num = 1;
	sysenv->log_level = DEBUG;

	strcpy(sysenv->log_pathfilename, LOG_DEFAULT_PATHFILENAME);
	strcpy(sysenv->config_pathfilename, CONF_DEFAULT_PATHFILENAME);

	/* parse command arguments and init system log file*/
	sb_parse_arg(sysenv, sb_argc, sb_os_argv);
	/* !!!now we can use log!!!! */
#endif

	if (sysenv->http_proxy) 
		workerenv->http_proxy = 1;

	/* parse config file to global sb_cycle_env */
				SystemInfo("FAILED:parse config file [%s] error", sysenv->config_pathfilename);
	if (sb_parse_conf(workerenv, sysenv->config_pathfilename)) {
		SystemInfo("FAILED:parse config file [%s] error", sysenv->config_pathfilename);
		return SB_ERROR;
	}
	/* now can use log */

	/* worker num & mode*/
	if (workerenv->workers_num) {
		sysenv->workers_num = workerenv->workers_num < SB_MAX_WORKERS_NUM ? workerenv->workers_num : SB_MAX_WORKERS_NUM;
	}
	workerenv->workers_num = sysenv->workers_num;
	if (workerenv->workers_num <= 0) {
		SystemInfo("FAILED:workers[%d] not correct\n", workerenv->workers_num); 
		return SB_ERROR;
	}

	sysenv->http_proxy = workerenv->http_proxy;

	return SB_OK;
}

/* @ FAILED: exit */
void sb_get_cpuinfo()
{
	sb_ncpu = sb_get_cpu_num();
	if (sb_ncpu == -1) {
		SystemInfo("FAILED:get cpu number failed"); 
		exit(1);
	}
}

int sb_alloc_channel(struct sb_channel **channel, struct sb_env *sysenv)
{
	/* alloc channels, notice channels always workersnum + 1 */
	*channel = (struct sb_channel*)malloc(sizeof(struct sb_channel) * (sysenv->workers_num + 1));
	if (!(*channel)) {
		return SB_ERROR;
	}

	return SB_OK;
}

int sb_init_accept_lock(struct sb_env *sysenv, struct sb_cycle_env *workerenv)
{
	/* init shm - init accept lock  */
	sysenv->shmid = shm_create(sizeof(sb_atomic_t)); /* accept lock */
	if (sysenv->shmid == -1) {
		return SB_ERROR;
	}

	sysenv->accept_lock = (sb_atomic_t*)shm_attach(sysenv->shmid);

	sb_spininit(sysenv->accept_lock);
	workerenv->accept_lock = sysenv->accept_lock;

	return SB_OK;	
}

void sb_destory_accept_lock(struct sb_env *sysenv, struct sb_cycle_env *workerenv)
{
	if (shm_detach((void*)sysenv->accept_lock)) {
		ErrorOutput("detach shmid[%d] failed, errno[%d]-error[%s]", sysenv->shmid, errno, strerror(errno));
	}
	if (shm_remove(sysenv->shmid)) {
		ErrorOutput("remove shmid[%d] failed, errno[%d]-error[%s]", sysenv->shmid, errno, strerror(errno));
	}

	sysenv->accept_lock = NULL;
	workerenv->accept_lock = NULL;
	sysenv->shmid = 0;
}

int sb_init_msgqueue(struct sb_env *sysenv, struct sb_cycle_env *workerenv)
{
	sysenv->msgqueueid = msg_create();
	if (sysenv->msgqueueid == -1) {
		return SB_ERROR;
	}
	workerenv->msgqueueid = sysenv->msgqueueid;

	return SB_OK;
}

void sb_destory_msgqueue(struct sb_env *sysenv, struct sb_cycle_env *workerenv)
{
	if (msg_remove(sysenv->msgqueueid)) {
		ErrorOutput("remove msgqueueid[%d] failed, errno[%d]-error[%s]", sysenv->msgqueueid, errno, strerror(errno));
	}
	sysenv->msgqueueid = 0;
	workerenv->msgqueueid = 0;
}

int main(int argc, char *argv[])
{
	struct timeval tv;
	struct sb_env *tmp_sysenv;
	struct sb_cycle_env *tmp_workerenv;
	struct sb_channel *tmp_channel;
	struct itimerval itv;
	int nfds;
	fd_set rset, wset, eset;
	sigset_t sgset;
	int rc;
	int i;
	int reload_complete;

	sb_update_time(&cache_time, cache_str_time);

	g_sysenv = &sysenv;
	g_workerenv = &workerenv;
	memset(&sysenv, 0x00, sizeof(sysenv));
	memset(&workerenv, 0x00, sizeof(workerenv));

	/* set no buffer */
	sb_set_io_nobuffer(stderr);
	sb_set_io_nobuffer(stdout);

	/* save process's argv */
	if (sb_save_argv_env(argc, argv) == SB_OK) {
		printf("SUCCESS:save arg & environ!\n");
	}
	else {
		printf("FAILED:save arg & environ!");
		exit(1);
	}

        g_sysenv->workers_num = 1;
        g_sysenv->log_level = DEBUG;

        strcpy(g_sysenv->log_pathfilename, LOG_DEFAULT_PATHFILENAME);
        strcpy(g_sysenv->config_pathfilename, CONF_DEFAULT_PATHFILENAME);

        /* parse command arguments and init system log file*/
        sb_parse_arg(g_sysenv, argc, argv);
        /* !!!now we can use log!!!! */

	/* parse arg & load configure file */
	if (sb_init_conf(g_sysenv, g_workerenv)) {
		SystemInfo("FAILED:load configure!");
	}
	SystemInfo("SUCCESS:load configure!");

	/* waa!!! now we can use log!!!! */

	sb_get_cpuinfo();
	SystemInfo("SUCCESS:cpu number[%d]!", sb_ncpu);

	/* show conf */
	sb_conf_show(g_workerenv);

	if (sb_alloc_channel(&g_channel, g_sysenv) != SB_OK) {
		ErrorOutput("FAILED:sb_alloc_channel failed");
		exit(1);
	}
	SystemInfo("SUCCESS:alloc channel!");

	/* init signal */
	sb_init_system_signal();
	SystemInfo("SUCCESS:init system signal!");

	if (g_sysenv->http_proxy) {
		/* init dns msg queue */

		if (sb_init_msgqueue(g_sysenv, g_workerenv) !=SB_OK) {
			SystemInfo("FAILED:msgget failed, errno[%d]-error[%s]", errno, strerror(errno));
			exit(1);
		}
		SystemInfo("SUCCESS:init msg msgqueueid [%d]", g_sysenv->msgqueueid);

		/* spawn dns */	
		if (sb_spawn_dns_process(g_sysenv, g_channel)) {
			SystemInfo("FAILED:spawn dns process");
			exit(1);
		}
		SystemInfo("SUCCESS:spawn dns process");
	}

	/* init listening */
	rc = sb_system_init_listening(g_workerenv);
	if (rc) {
		ErrorOutput("FAILED:init listenging failed");
		if (g_sysenv->http_proxy) {
			sb_destory_msgqueue(g_sysenv, g_workerenv);
		}
		exit(1);
	}
	SystemInfo("SUCCESS:init listening!");

	if (sb_init_accept_lock(g_sysenv, g_workerenv) != SB_OK) {
		SystemInfo("FAILED:create shm failed, errno[%d]-error[%s]", errno, strerror(errno));
		if (g_sysenv->http_proxy) {
			sb_destory_msgqueue(g_sysenv, g_workerenv);
		}
		exit(1);
	}
	SystemInfo("SUCCESS:create(accept lock) shm shmid [%d]!", g_sysenv->shmid);

	/* lock accept, the worker process can't enter until unlock by master process*/ 
	sb_spinlock(g_sysenv->accept_lock);

	/* spawn worker */
	if (sb_spawn_workers_process(g_workerenv, g_channel)) {

		SystemInfo("FAILED:sb_spawn_workers_process failed");

		/* kill workers by force */
		(void)sb_kill_workers(g_sysenv, g_channel, 1);

		if (g_sysenv->http_proxy) {
			(void)sb_kill_dns_server(g_sysenv, g_channel, 1);
		}

		goto ERR;
	}
	SystemInfo("SUCCESS:spawn worker process!");

	/* not close master's listen port */

	SystemInfo("master process enter main loop!");
	/* 300ms */
	tv.tv_sec = 0;
	tv.tv_usec = 100 * 1000;

	itv.it_interval.tv_sec = 60;	/* 60s */
	itv.it_interval.tv_usec = 0;	
	itv.it_value.tv_sec = 60;	/* 60s */
	itv.it_value.tv_usec = 0;

	/* unlock accept */
	sb_spinunlock(g_sysenv->accept_lock);

	/* mask signal */
	sigemptyset(&sgset);
	for (i = 1; i <= 64; i++) {
		sigaddset(&sgset, i);
	}
	sigprocmask(SIG_BLOCK, &sgset, NULL);
	sigemptyset(&sgset);

	sb_system_show_process(g_sysenv, g_channel);
	
MAINLOOP:
	sb_update_time(&cache_time, cache_str_time);
	/* main loop */
	while (1) {
		if (status == SB_STATUS_RELOADING) {
			tmp_sysenv = g_new_sysenv;
			tmp_channel = g_new_channel;
			tmp_workerenv = g_new_workerenv;

		} else {
			tmp_sysenv = g_sysenv;
			tmp_channel = g_channel;
			tmp_workerenv = g_workerenv;
		}

		if (g_sysenv->http_proxy) {
			msgtype_start = 1;
			if (sb_reload_seq_flag == 0) {
				msgtype_start += SB_MAX_WORKERS_NUM;
			}
			for (i = msgtype_start; i < msgtype_num + msgtype_start; i++) {
				if (sb_proxy_discard_resp(&tmp_workerenv, i)) {
					ErrorOutput("discard msg type[%d] error", i);
				}
			}

			if (setitimer(ITIMER_REAL, &itv, NULL)) {
				ErrorOutput("setitimer error, errno[%d]", errno);
			}
		}

		/* suspend main loop */
		sigsuspend(&sgset);

		if (sb_system_process_signal(tmp_sysenv, tmp_channel)) {
			ErrorOutput("process signal error");	
			status = 0;
			sigcmd = 0;
		}

		if (sigcmd == SB_SYS_CMD_QUICKEXIT || sigcmd == SB_SYS_CMD_SLOWEXIT) {
			SystemInfo("catch exit command, break main loop");
			break;
		}

		if (status == 0 && g_sysenv->http_proxy) {
			if (g_channel[0].pid <= 0) {
				close(g_channel[0].channel[0]);
				close(g_channel[0].channel[1]);

				rc = sb_respawn_child_process(g_workerenv, g_channel, 0);
				if (rc) {
					FatalOutput("restart dns server failed");
					goto EXIT;
				}
			}
		}
		
		if (status == 0) {
			for (i = 1; i <= tmp_sysenv->workers_num; i++) {
				if (tmp_channel[i].pid <= 0) {
					close(tmp_channel[i].channel[0]);
					close(tmp_channel[i].channel[1]);

					SystemInfo("pid [%d] exit, restarting", -tmp_channel[i].pid );
					if (sb_respawn_child_process(tmp_workerenv, tmp_channel, i)) {
						FatalOutput("restart worker process - no[%d] failed", i);
						goto EXIT;
					}
				}
			}
		}

		/* communicate with children */
		FD_ZERO(&rset);
		if (g_sysenv->http_proxy) {
			FD_SET(g_channel[0].channel[0], &rset);
			if (nfds < g_channel[0].channel[0] + 1) {
				nfds = g_channel[0].channel[0] + 1;
			}
		}

		for (i = 1; i <= tmp_sysenv->workers_num; i++) {
			if (tmp_channel[i].channel[0] > 0) {
				FD_SET(tmp_channel[i].channel[0], &rset);
				if (nfds < tmp_channel[i].channel[0] + 1) {
					nfds = tmp_channel[i].channel[0] + 1;
				}
			}
		}

		while (1) {
			rc = select(nfds, &rset, NULL, NULL, &tv); 
			if (rc == -1) {
				if (errno == EINTR) {
					continue;
				}
				FatalOutput("select readable fd failed, error[%d]-error[%s]", errno, strerror(errno));
				goto EXIT;
			}
			if (rc) {
				if (tmp_sysenv->http_proxy) {
					if (FD_ISSET(tmp_channel[0].channel[0], &rset)) {
						sb_system_process_dns_read(tmp_channel[0].channel[0], &tmp_channel[0]);
					}
				}
				for (i = 1; i <= tmp_sysenv->workers_num; i++) {
					if (tmp_channel[i].channel[0] > 0) {
						if (FD_ISSET(tmp_channel[i].channel[0], &rset)) {
							sb_system_process_worker_read(tmp_channel[i].channel[0], &tmp_channel[i]);
						}
					}
				}
			}

			break;
		}

		if (status == SB_STATUS_RELOADING) {
			reload_complete = 1;
			for (i = 1; i < g_new_sysenv->workers_num; i++) {
				if (g_new_channel[i].pid <= 0) {
					SystemInfo("pid [%d] exit, restarting", g_new_channel[i].pid);
					reload_complete = 0;
					break;
				}
			}

			if (reload_complete == 1) {

				/* free old accept lock (shm) */
				sb_destory_accept_lock(g_sysenv, g_workerenv);

				/* free conf */
				sb_free_conf(g_workerenv);

				/* free channel */
				free(g_channel);
				g_channel = NULL;

				memcpy(g_sysenv, g_new_sysenv, sizeof(struct sb_env));
				memset(g_new_sysenv, 0x00, sizeof(struct sb_env));

				memcpy(g_workerenv, g_new_workerenv, sizeof(struct sb_cycle_env));
				memset(g_new_workerenv, 0x00, sizeof(struct sb_cycle_env));

				g_channel = g_new_channel;	
				g_new_channel = NULL;

				SystemInfo("SUCCESS:reload configure success!");

			}
				sigcmd = 0;
				status = 0;	
		}
	}

EXIT:	/* (1) normal exit(slow or quick exit); (2) enter main loop occur error */

	/* lock accept */
	sb_system_lock_accept(g_sysenv);

	if (sigcmd == SB_SYS_CMD_QUICKEXIT) {

		SystemInfo("quick exiting ...");
		if (g_sysenv->http_proxy) {
			sb_kill_dns_server(g_sysenv, g_channel, 1);
		}
		sb_kill_workers(g_sysenv, g_channel, 1);

	}  else if (sigcmd == SB_SYS_CMD_SLOWEXIT) {

		SystemInfo("slow exiting ...");

		if (g_sysenv->http_proxy) {
			sb_kill_dns_server(g_sysenv, g_channel, 0);
		}
		sb_kill_workers(g_sysenv, g_channel, 0);

		int loop = 1200;
		while (loop--) {

			if (g_sysenv->http_proxy) {
				if (g_channel[0].pid > 0) {
					continue;
				} 
			}

			for (i = 1; i < g_sysenv->workers_num; i++) {
				if (g_channel[i].pid > 0) {
					continue;
				}
			}

			/* 1s */
			usleep( 1000000);
			SystemInfo("left times %d second", loop);
		}

	} else { /* error occur, kill dns and worker process by force */	

		SystemInfo("fatal error lead exiting ...");

		if (g_sysenv->http_proxy) {
			sb_kill_dns_server(g_sysenv, g_channel, 1);
		}

		sb_kill_workers(g_sysenv, g_channel, 1);

		if (status == SB_STATUS_RELOADING) {
			sb_kill_workers(g_new_sysenv, g_new_channel, 1);
		}
	}

	sb_destory_accept_lock(g_sysenv, g_workerenv);
	SystemInfo("remove shmid [%d]", g_sysenv->shmid);

	sb_destory_msgqueue(g_sysenv, g_workerenv);
	SystemInfo("remove msgid [%d]", g_sysenv->msgqueueid);

	if (status == SB_STATUS_RELOADING) {
		sb_destory_accept_lock(g_new_sysenv, g_new_workerenv);
		SystemInfo("reloading: remove shmid [%d]", g_sysenv->shmid);

		sb_destory_msgqueue(g_new_sysenv, g_new_workerenv);
		SystemInfo("realoading: remove msgid [%d]", g_new_sysenv->msgqueueid);
	}

	if (sigcmd != 0) {
		return 0;
	} else {
		return 1;	
	}

ERR:	/* before main loop find occur, clean system resource */

	SystemInfo("error occur, exiting...");

	sb_destory_accept_lock(g_sysenv, g_workerenv);
	SystemInfo("remove shmid [%d]", g_sysenv->shmid);

	sb_destory_msgqueue(g_sysenv, g_workerenv);
	SystemInfo("remove msgid [%d]", g_sysenv->msgqueueid);

	return 2;
}
